#include <buola/scene/cimagerenderer.h>

#include <buola/gui/ctoolkit.h>
#include <buola/image/cgc.h>

namespace buola { namespace scene {

using namespace gui;

CImageRenderer::CImageRenderer()
    :   CRenderer(FLAG_FLIP)
    ,   mHaveBuffers(false)
{
}

CImageRenderer::~CImageRenderer()
{
}

void CImageRenderer::GetImage(const img::CMView_rgb8 &pImage)
{
    CreateBuffers(CSize_i(pImage.Width(),pImage.Height()));
    InitContext(CSize_i(pImage.Width(),pImage.Height()),1);
    Render();

    mFrameBuffer.Bind(gl::CFrameBuffer::READ);

    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glReadPixels(0,0,pImage.Width(),pImage.Height(),GL_RGB,GL_UNSIGNED_BYTE,pImage.RawData());

    mFrameBuffer.Unbind(gl::CFrameBuffer::DRAW);
    mFrameBuffer.Unbind(gl::CFrameBuffer::READ);

    if(glGetError()) msg_warn() << "error in GetImage GL context\n";
}

void CImageRenderer::GetImage(const img::CMView_rgb8 &pImage,const img::CMView_gray32 &pDepth)
{
    CreateBuffers(CSize_i(pImage.Width(),pImage.Height()));
    InitContext(CSize_i(pImage.Width(),pImage.Height()),1);
    Render();

    mFrameBuffer.Bind(gl::CFrameBuffer::READ);

    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glReadPixels(0,0,pImage.Width(),pImage.Height(),GL_RGB,GL_UNSIGNED_BYTE,pImage.RawData());
    glReadPixels(0,0,pDepth.Width(),pDepth.Height(),GL_DEPTH_COMPONENT,GL_UNSIGNED_INT,pDepth.RawData());

    mFrameBuffer.Unbind(gl::CFrameBuffer::DRAW);
    mFrameBuffer.Unbind(gl::CFrameBuffer::READ);

    if(glGetError()) msg_warn() << "error in GetImage GL context\n";
}

void CImageRenderer::GetImage(const img::CMView_rgb8 &pImage,const img::CMView_gray32 &pDepth,
                              const img::CMView_gray8 &pTransformIndex,std::vector<PTransform> &pTransforms)
{
    CreateBuffers(CSize_i(pImage.Width(),pImage.Height()));
    InitContext(CSize_i(pImage.Width(),pImage.Height()),2);
    RenderWithTransformIndex(pTransforms);

    //this is a hack needed for it to work on mac... no idea why, but it seems that when both an integer and a regular
    //render buffer are attached, reading pixels doesn't work for the regular one, and requires to pass GL_RGB_INTEGER instead
    //which is both ugly and incorrect
    glDrawBuffers(0,nullptr);
    mFrameBuffer.Bind(gl::CFrameBuffer::READ);

    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glReadPixels(0,0,pImage.Width(),pImage.Height(),GL_RGB,GL_UNSIGNED_BYTE,pImage.RawData());
    glReadPixels(0,0,pDepth.Width(),pDepth.Height(),GL_DEPTH_COMPONENT,GL_UNSIGNED_INT,pDepth.RawData());
    glReadBuffer(GL_COLOR_ATTACHMENT1);
    glReadPixels(0,0,pImage.Width(),pImage.Height(),GL_RED_INTEGER,GL_UNSIGNED_BYTE,pTransformIndex.RawData());

    mFrameBuffer.Unbind(gl::CFrameBuffer::DRAW);
    mFrameBuffer.Unbind(gl::CFrameBuffer::READ);

    if(glGetError()) msg_warn() << "error in GetImage GL context\n";
}

void CImageRenderer::GetImage(const img::CMView_gray8 &pImage,const img::CMView_gray32 &pDepth)
{
    CreateBuffers(CSize_i(pImage.Width(),pImage.Height()));
    InitContext(CSize_i(pImage.Width(),pImage.Height()),1);
    Render();

    mFrameBuffer.Bind(gl::CFrameBuffer::READ);

    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glReadPixels(0,0,pImage.Width(),pImage.Height(),GL_RED,GL_UNSIGNED_BYTE,pImage.RawData());
    glReadPixels(0,0,pDepth.Width(),pDepth.Height(),GL_DEPTH_COMPONENT,GL_UNSIGNED_INT,pDepth.RawData());

    mFrameBuffer.Unbind(gl::CFrameBuffer::DRAW);
    mFrameBuffer.Unbind(gl::CFrameBuffer::READ);

    if(glGetError()) msg_warn() << "error in GetImage GL context\n";
}

void CImageRenderer::InitContext(const CSize_i &pSize,int pNumBuffers)
{
    mFrameBuffer.Bind(gl::CFrameBuffer::DRAW);

    glViewport(0,0,pSize.x,pSize.y);
    glDisable(GL_SCISSOR_TEST);

    GLenum lIDs[]={GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1};
    glDrawBuffers(1,&lIDs[0]);
    gl::clear_color(GetClearColor());
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glDrawBuffers(1,&lIDs[1]);
    gl::clear_color(img::CColor(1.0,0.0,0.0));
    glClear(GL_COLOR_BUFFER_BIT);
    glDrawBuffers(pNumBuffers,lIDs);

    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
    glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ONE);
    glPixelStorei(GL_UNPACK_ALIGNMENT,1);

    mGC->ResetState();
}

void CImageRenderer::CreateBuffers(const CSize_i &pSize)
{
    if(!mGC)
    {
        mGC.reset(get_toolkit()->GetOpenGLGC());
    }
    mGC->MakeCurrent();

    if(!mHaveBuffers)
    {
        mFrameBuffer.Create();
        mFrameBuffer.Bind(gl::CFrameBuffer::DRAW);

        mRenderBuffer.Create();
        mRenderBuffer.Bind();
        mRenderBuffer.SetupStorage(GL_RGB8,pSize);

        mDepthBuffer.Create();
        mDepthBuffer.Bind();
        mDepthBuffer.SetupStorage(GL_DEPTH_COMPONENT32,pSize);

        mTransformIndexBuffer.Create();
        mTransformIndexBuffer.Bind();
        mTransformIndexBuffer.SetupStorage(GL_R8UI,pSize);

        mFrameBuffer.AttachColor(gl::CFrameBuffer::DRAW,mRenderBuffer,0);
        mFrameBuffer.AttachColor(gl::CFrameBuffer::DRAW,mTransformIndexBuffer,1);
        mFrameBuffer.AttachDepth(gl::CFrameBuffer::DRAW,mDepthBuffer);

        mSize=pSize;
        mHaveBuffers=true;
    }
    else if(mSize!=pSize)
    {
        mRenderBuffer.Bind();
        mRenderBuffer.SetupStorage(GL_RGB8,pSize);
        mDepthBuffer.Bind();
        mDepthBuffer.SetupStorage(GL_DEPTH_COMPONENT32,pSize);
        mTransformIndexBuffer.Bind();
        mTransformIndexBuffer.SetupStorage(GL_R8UI,pSize);
        mSize=pSize;
    }
}

/*namespace gl*/ } /*namespace buola*/ }
